package edu.hcmus.sow.web.document;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import edu.hcmus.sow.domain.File;
import edu.hcmus.sow.domain.FileAccessRight;
import edu.hcmus.sow.domain.FileRevision;
import edu.hcmus.sow.domain.FileRevisionComment;
import edu.hcmus.sow.domain.Folder;
import edu.hcmus.sow.domain.User;
import edu.hcmus.sow.domain.UserProfile;
import edu.hcmus.sow.domain.repository.FileInfo;
import edu.hcmus.sow.service.DocumentService;
import edu.hcmus.sow.service.RepositoryService;
import edu.hcmus.sow.service.SecurityService;
import edu.hcmus.sow.service.UserService;
import edu.hcmus.sow.utils.SecurityUtil;
import edu.hcmus.sow.utils.StringUtil;

/**
 * Spring MVC controller that handles CRUD requests for File entities
 * 
 */
@Controller
public class FileController {

   // ~ Instance fields
   // ================================================================================================
   @Autowired
   private DocumentService documentService;

   @Autowired
   private RepositoryService repositoryService;

   @Autowired
   private UserService userService;

   @Autowired
   private SecurityService securityService;

   // ~ File's methods
   // ================================================================================================
   @RequestMapping(value = "/file/upload", method = RequestMethod.GET)
   public ModelAndView uploads(@RequestParam("p") Integer p) {
      ModelAndView mav = new ModelAndView();

      mav.addObject("p", p);
      mav.setViewName("file_upload");
      return mav;
   }

   @RequestMapping(value = "/file/upload", method = RequestMethod.POST)
   public void upload(@RequestParam("folderID") Integer folderID, HttpServletRequest request) {
      Folder folder = documentService.getFolderByID(folderID);
      Folder root = getRootFolder(folder);
      if (securityService.hasPermission(root, BasePermission.WRITE) || securityService.hasRole("ROLE_ADMINISTRATOR")) {
         try {
            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload();

            // Parse the request
            FileItemIterator iter = upload.getItemIterator(request);
            while (iter.hasNext()) {
               FileItemStream item = iter.next();
               String name = item.getFieldName();
               InputStream stream = item.openStream();
               if (!item.isFormField()) {
                  System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
                  // Process the input stream
                  FileInfo fi = repositoryService.saveFile(stream);
                  System.out.println(fi.getRowkey());

                  String fileName = item.getName();

                  // if file name exists
                  if (documentService.findFileByName(fileName) != null) {
                     int index = StringUtils.lastIndexOf(fileName, '.');
                     String temp = fileName.substring(0, index) + "_" + System.currentTimeMillis()
                           + fileName.substring(index);
                     fileName = temp;
                  }

                  // save file in database
                  File file = new File();
                  file.setName(fileName);
                  file.setFolder(folder);
                  file.setDefaultAccessRight("F");

                  file = documentService.saveFile(file);

                  // get logged in user
                  int userID = SecurityUtil.getCurrentLoggedinUserID();
                  User user = userService.getUserByID(userID);

                  // save revision
                  FileRevision fr = new FileRevision();
                  fr.setFile(file);
                  fr.setName(fileName);
                  fr.setSize(fi.getSize());
                  fr.setType(item.getContentType());
                  fr.setRowkey(fi.getRowkey());
                  fr.setRevision(1);
                  fr.setCreatedDate(Calendar.getInstance());
                  fr.setUserByCreatedBy(user);

                  documentService.saveFileRevision(fr);
               }
            }
         } catch (Exception e) {
            e.printStackTrace();
         }
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping("/file/{id}/download")
   public void download(@PathVariable("id") Integer id, @RequestParam("rk") String rk, HttpServletResponse response) {
      // get file by id
      FileRevision file = documentService.getFileRevisionByID(id);
      Folder folder = documentService.getFolderByID(file.getFile().getFolderID());
      Folder root = getRootFolder(folder);
      String accessRight = documentService.getAccessRightByFile(file.getFile());
      if ((securityService.hasPermission(root, BasePermission.READ) && securityService.hasAccessRight(accessRight,
            SecurityUtil.READ, SecurityUtil.FULL)) || securityService.hasRole("ROLE_ADMINISTRATOR")) {
         // if exist file
         if (file != null) {
            response.setContentType(file.getType());
            response.setContentLength((int) file.getSize().longValue());
            response.setHeader("Content-Disposition", "attachment;filename=\"" + file.getName() + "\"");

            try {
               repositoryService.readFile(file.getRowkey(), response.getOutputStream());
               response.flushBuffer();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping("/file/{id}/delete")
   public String deletefile(@PathVariable("id") Integer id) {
      // get file revision by id
      FileRevision fr = documentService.getFileRevisionByID(id);
      Folder folder = documentService.getFolderByID(fr.getFile().getFolderID());
      Folder root = getRootFolder(folder);
      String accessRight = documentService.getAccessRightByFile(fr.getFile());
      if ((securityService.hasPermission(root, BasePermission.WRITE) && securityService.hasAccessRight(accessRight,
            SecurityUtil.FULL)) || securityService.hasRole("ROLE_ADMINISTRATOR")) {

         // get file
         File file = fr.getFile();
         // get parent folder id
         Integer folderID = file.getFolderID();

         // delete data on cassandra
         List<FileRevision> filesRevision = documentService.getFilesRevisionByFileID(file.getId());
         for (FileRevision fileRevision : filesRevision) {
            repositoryService.deleteFile(fileRevision.getRowkey());
         }

         // delete file
         documentService.deleteFile(file.getId());

         return "redirect:/folder/" + folderID;
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping("/document/{id}")
   public ModelAndView viewDocument(@PathVariable("id") Integer id) {
      // get info selected revision
      FileRevision fr = documentService.getFileRevisionByID(id);

      Folder currentFolder = documentService.getFolderByID(fr.getFile().getFolderID());
      Folder root = getRootFolder(currentFolder);
      String accessRight = documentService.getAccessRightByFile(fr.getFile());
      if ((securityService.hasPermission(root, BasePermission.READ) && securityService.hasAccessRight(accessRight,
            SecurityUtil.READ, SecurityUtil.FULL)) || securityService.hasRole("ROLE_ADMINISTRATOR")) {
         ModelAndView mav = new ModelAndView();

         List<Folder> rootFolders = getRootFolders(fr.getFile().getFolderID());

         UserProfile user = new UserProfile();
         if (fr.getUserByApprovedBy() != null) {
            user = userService.getUserProfileByUserID(fr.getUserByApprovedBy().getId());
         }

         // get file revisions list
         List<FileRevision> fileRevisions = documentService.getFilesRevisionByFileID(fr.getFile().getId());

         // get comments list
         List<FileRevisionComment> comments = documentService.getCommentsByFileRevisionID(fr.getId());

         mav.addObject("fileRevisions", fileRevisions);
         mav.addObject("comments", comments);
         mav.addObject("file", fr);
         mav.addObject("approver", user);
         mav.setViewName("document_detail");

         mav.addObject("rootFolders", rootFolders);
         boolean isWrite = ((securityService.hasPermission(root, BasePermission.WRITE) && securityService
               .hasAccessRight(accessRight, SecurityUtil.FULL)) || securityService.hasRole("ROLE_ADMINISTRATOR"));
         mav.addObject("isWrite", isWrite);
         mav.addObject("isOwner", fr.getUserByCreatedBy().getId().intValue() == SecurityUtil.getCurrentLoggedinUserID()
               .intValue());

         return mav;
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping(value = "/ajax/file/{id}/rename", method = RequestMethod.POST)
   @ResponseBody
   public String renameFolder(@PathVariable("id") Integer id, @RequestParam("fileName") String fileName) {
      // get file
      File file = documentService.getFileByID(id);

      // validate
      String message = validate(file.getFolderID(), fileName);

      if (!file.getName().equalsIgnoreCase(fileName.trim())) {
         if (!"".equals(message))
            return message;
      }

      file.setName(fileName.trim());
      // rename file
      documentService.saveFile(file);

      return "";
   }

   @RequestMapping(value = "ajax/file/upload", method = RequestMethod.POST)
   public String ajaxUpload(@RequestParam("currentRevisionID") Integer currentRevisionID, HttpServletRequest request) {
      FileRevision currentRevision = documentService.getFileRevisionByID(currentRevisionID);

      Folder currentFolder = documentService.getFolderByID(currentRevision.getFile().getFolderID());
      Folder root = getRootFolder(currentFolder);
      String accessRight = documentService.getAccessRightByFile(currentRevision.getFile());
      if ((securityService.hasPermission(root, BasePermission.WRITE) && securityService.hasAccessRight(accessRight,
            SecurityUtil.FULL)) || securityService.hasRole("ROLE_ADMINISTRATOR")) {
         try {
            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload();

            // Parse the request
            FileItemIterator iter = upload.getItemIterator(request);
            while (iter.hasNext()) {
               FileItemStream item = iter.next();
               // String name = item.getFieldName();
               InputStream stream = item.openStream();
               if (!item.isFormField()) {
                  // Process the input stream
                  FileInfo fi = repositoryService.saveFile(stream);

                  String fileName = item.getName();

                  // get logged in user
                  int userID = SecurityUtil.getCurrentLoggedinUserID();
                  User user = userService.getUserByID(userID);

                  // save revision
                  FileRevision fr = new FileRevision();
                  fr.copy(currentRevision);
                  fr.setId(null);
                  fr.setName(fileName);
                  fr.setSize(fi.getSize());
                  fr.setType(item.getContentType());
                  fr.setRowkey(fi.getRowkey());
                  fr.setRevision(fr.getRevision() + 1);
                  fr.setCreatedDate(Calendar.getInstance());
                  fr.setUserByCreatedBy(user);

                  fr = documentService.saveFileRevision(fr);

                  return "redirect:/document/" + fr.getId();
               }
            }
         } catch (Exception e) {
            e.printStackTrace();
         }
         return "error";
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping(value = "/ajax/file/{id}/addApprover", method = RequestMethod.POST)
   public ModelAndView addApprover(@PathVariable("id") Integer id, @RequestParam("approverID") Integer approverID) {
      ModelAndView mav = new ModelAndView();
      // get file revision
      FileRevision fr = documentService.getFileRevisionByID(id);

      if (fr.getUserByCreatedBy().getId().intValue() == SecurityUtil.getCurrentLoggedinUserID().intValue()) {
         // get user
         UserProfile user = userService.getUserProfileByUserID(approverID);

         // set approve
         fr.setApprovalDueDate(null);
         fr.setApprovalReminder(null);
         fr.setUserByApprovedBy(user.getUserByUserId());
         fr.setApprovalStatus("W");
         // update
         fr = documentService.saveFileRevision(fr);

         mav.addObject("file", fr);
         mav.addObject("approver", user);
         mav.addObject("succes", "succes");
         mav.setViewName("controls/c_approval_form");
         return mav;
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping(value = "/ajax/file/{id}/removeApprover", method = RequestMethod.POST)
   public ModelAndView removeApprover(@PathVariable("id") Integer id) {
      ModelAndView mav = new ModelAndView();
      // get file revision
      FileRevision fr = documentService.getFileRevisionByID(id);
      if (fr.getUserByCreatedBy().getId().intValue() == SecurityUtil.getCurrentLoggedinUserID().intValue()) {
         // set approve
         fr.setApprovalDueDate(null);
         fr.setApprovalReminder(null);
         fr.setUserByApprovedBy(null);
         fr.setApprovalStatus(null);
         fr.setApprovedDate(null);
         fr.setReason(null);
         // update
         fr = documentService.saveFileRevision(fr);

         mav.addObject("file", fr);
         mav.addObject("succes", "succes");
         mav.setViewName("controls/c_approval_form");
         return mav;
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping(value = "/ajax/file/{id}/approveOrRejectFile", method = RequestMethod.POST)
   public ModelAndView approveOrRejectFile(@PathVariable("id") Integer id, @RequestParam("reason") String reason,
         @RequestParam("status") String status) {
      ModelAndView mav = new ModelAndView();
      // get file revision
      FileRevision fr = documentService.getFileRevisionByID(id);
      if (fr.getUserByApprovedBy().getId().intValue() == SecurityUtil.getCurrentLoggedinUserID().intValue()) {
         UserProfile user = userService.getUserProfileByUserID(fr.getUserByApprovedBy().getId());

         // set approval
         fr.setApprovedDate(Calendar.getInstance());
         fr.setReason(reason.trim());
         fr.setApprovalStatus(status.trim());
         // update
         fr = documentService.saveFileRevision(fr);

         mav.addObject("file", fr);
         mav.addObject("approver", user);
         mav.addObject("succes", "succes");
         mav.setViewName("controls/c_approval_form");
         return mav;
      } else {
         throw new AccessDeniedException("Access Denied");
      }
   }

   @RequestMapping(value = "/ajax/file/{id}/addComment", method = RequestMethod.POST)
   public ModelAndView addComment(@PathVariable("id") Integer id, @RequestParam("content") String content) {
      ModelAndView mav = new ModelAndView();
      // get file revision
      FileRevision fr = documentService.getFileRevisionByID(id);

      // get logged in user
      int userID = SecurityUtil.getCurrentLoggedinUserID();
      User user = userService.getUserByID(userID);

      FileRevisionComment comment = new FileRevisionComment();
      // set approval
      comment.setFileRevision(fr);
      comment.setContent(content.trim());
      comment.setCreatedDate(Calendar.getInstance());
      comment.setUser(user);
      // save
      comment = documentService.saveFileRevisionComment(comment);

      // get comments list
      List<FileRevisionComment> comments = documentService.getCommentsByFileRevisionID(fr.getId());

      mav.addObject("file", fr);
      mav.addObject("comments", comments);
      mav.setViewName("controls/c_file_comment");
      return mav;
   }

   @RequestMapping(value = "/ajax/document/{id}/permissions", method = RequestMethod.GET)
   public ModelAndView viewPermission(@PathVariable("id") Integer id) {
      ModelAndView mav = new ModelAndView();
      // get file
      File f = documentService.getFileByID(id);

      // get permissions
      List<FileAccessRight> permissions = documentService.getPermissionsByFileID(f.getId());

      mav.addObject("file", f);
      mav.addObject("permissions", permissions);
      mav.setViewName("controls/file_access_right");

      Folder currentFolder = documentService.getFolderByID(f.getFolderID());
      Folder root = getRootFolder(currentFolder);
      boolean isAdmin = (securityService.hasPermission(root, BasePermission.ADMINISTRATION) || securityService
            .hasRole("ROLE_ADMINISTRATOR"));
      mav.addObject("isAdmin", isAdmin);

      return mav;
   }

   @RequestMapping(value = "/ajax/document/{id}/permission/add", method = RequestMethod.POST)
   public ModelAndView addPermission(@PathVariable("id") Integer id, @RequestParam("newUserIDs") String newUserIDs,
         @RequestParam("newPerm") String newPerm, @RequestParam("pIDs[]") Integer[] pIDs,
         @RequestParam("perms[]") String[] perms) {
      ModelAndView mav = new ModelAndView();

      // update default access right
      File f = documentService.getFileByID(id);
      f.setDefaultAccessRight(perms[0]);
      documentService.saveFile(f);

      // update users's access right
      for (int i = 1; i < pIDs.length; i++) {
         FileAccessRight far = documentService.getPermissionByID(pIDs[i]);
         far.setAccessRight(perms[i]);
         documentService.saveFileAccessRight(far);
      }

      // add new access right
      if (!StringUtils.isEmpty(newUserIDs.trim())) {
         String[] userIDs = newUserIDs.split(",");
         for (int i = 0; i < userIDs.length; i++) {
            User u = userService.getUserByID(Integer.parseInt(userIDs[i]));
            FileAccessRight newFar = documentService.findOrCreatePermission(f.getId(), u.getId());
            newFar.setFile(f);
            newFar.setUser(u);
            newFar.setAccessRight(newPerm);
            documentService.saveFileAccessRight(newFar);
         }
      }

      // view permissions
      mav = viewPermission(id);

      return mav;
   }

   @RequestMapping(value = "/ajax/document/{fileID}/permission/{id}/delete", method = RequestMethod.POST)
   public ModelAndView addPermission(@PathVariable("fileID") Integer fileID, @PathVariable("id") Integer id) {
      ModelAndView mav = new ModelAndView();

      // delete access right
      documentService.deleteFileAccessRight(id);

      // view permissions
      mav = viewPermission(fileID);

      return mav;
   }

   @RequestMapping("/welcome")
   public String printWelcome(HttpServletRequest request) {

      // repositoryService.createFileStorage("sow");

      // repositoryService.dropFileStorage("sow");
      System.out.println("OK");

      return "/dashboard";
   }

   private String validate(Integer folderID, String name) {
      String prefix = StringUtil.getPrefix(name);
      String fileName = StringUtils.trim(name);

      if (StringUtils.isEmpty(prefix.trim())) {
         return "File's name must not be blank.";
      }

      // check name exists
      if (documentService.fileExists(folderID, fileName)) {
         return "File's name is exists.";
      }
      return "";
   }

   private List<Folder> getRootFolders(Integer id) {
      Folder folder = documentService.getFolderByID(id);

      List<Folder> folders = new ArrayList<Folder>();
      while (folder.getParentID() != null) {
         folders.add(0, folder);
         folder = documentService.getFolderByID(folder.getParentID());
      }
      folders.add(0, folder);

      return folders;
   }

   private Folder getRootFolder(Folder folder) {
      while (folder.getParentID() != null) {
         folder = documentService.getFolderByID(folder.getParentID());
      }

      return folder;
   }
}